package arkham.data;

import java.awt.Color;
import java.awt.Image;
import java.util.*;

import arkham.data.Globals.*;
import arkham.jaxb.encounter.*;
import arkham.util.AHLogger;
import arkham.util.XmlTools;

/**
 * This represents a standard investigator card.
 * @author ctheng
 *
 */
public abstract class InvestigatorCard implements EffectSource, Comparable<InvestigatorCard>,
	NamedEntity, IdNumbered
{

	private String name;
	private String description;
	protected Image img;
	private int numCounters, limit, id;
	private boolean exhausted;
	private HashMap<String, String> properties;
	private List<EffectType> permEffects;
	private List<ModEffect> modEffects;
	private List<UsableEffect> usableEffects;

	/** Default constructor kept protected */
	protected InvestigatorCard(String name)
	{
		this.name = name;
		properties = new HashMap<String, String>();
		permEffects = new ArrayList<EffectType>();
		modEffects = new ArrayList<ModEffect>();
		usableEffects = new ArrayList<UsableEffect>();
		numCounters = 0;
	}

	/**
	 * Gets the type of the investigator card
	 * @return The type of the card
	 */
	public abstract InvestigatorCardType getType();

	/**
	 * Return true if this investigator card counts as an item.
	 * Items are tradeable and can be lost when investigators are
	 * asked to lose items
	 * @return true if this investigator card is an item
	 */
	public abstract boolean isItem();

	/**
	 * Gets the name of the card
	 * @return The name of the card
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * Gets the description on the card
	 * @return The description
	 */
	public String getDescription()
	{
		return description;
	}

	/**
	 * Sets the description on the card
	 * @param desc The description to set
	 */
	protected void setDescription(String desc)
	{
		this.description = desc;
	}

	/**
	 * Gets the preferred background color for displaying this type of
	 * investigator card. Subclasses are encouraged to overwrite this.
	 * @return The preferred background color for this investigator card
	 */
	public Color getPreferredColor()
	{
		return Color.white;
	}

	/**
	 * Gets the unique id of this card
	 * @return the id of this card
	 */
	public int getId()
	{
		return id;
	}

	/**
	 * Sets the unique id of this card
	 * @param id the id to set
	 */
	public void setId(int id)
	{
		this.id = id;
	}

	/**
	 * Sets if this item is exhausted for the turn
	 * @param exhausted set to true to exhaust the item, false to refresh
	 */
	public void setExhausted(boolean exhausted) {
		this.exhausted = exhausted;
	}

	/**
	 * Checks if a once a turn item has been used this turn
	 * @return true if this item is exhausted
	 */
	public boolean isExhausted() {
		return exhausted;
	}

	/**
	 * Gets the image on the card
	 * @return The image on the card
	 */
	public Image getImage()
	{
		return img;
	}

	/**
	 * Initializes the image on the card
	 * @param filename The filename of the image file
	 */
	public void setImage(String filename)
	{
		//TODO: to do this part
	}

	/**
	 * Sets the number of counters on this card
	 * @param numCounters the numCounters to set
	 */
	public void setNumCounters(int numCounters) {
		this.numCounters = numCounters;
	}

	/**
	 * Gets the number of counters on this card
	 * @return the numCounters
	 */
	public int getNumCounters() {
		return numCounters;
	}

    @Override
    public int getLimit()
    {
        return limit;
    }

    @Override
    public void setLimit(int limit)
    {
        this.limit = limit;
    }

	/**
	 * Checks if a property is defined for this investigator card
	 * @param property The property to check
	 * @return true if it is defined, false otherwise
	 */
	public boolean hasProperty(String property)
	{
		return properties.containsKey(property);
	}

	/**
	 * Gets a property of this investigator card
	 * @param property The name of the property
	 * @return The retrieved property
	 */
	public String getProperty(String property)
	{
		return properties.get(property);
	}

	/**
	 * Gets the list of permanent effects
	 * @return the list of permanent effects
	 */
	public List<EffectType> getPermEffects()
	{
		return permEffects;
	}

	/*
	 * (non-Javadoc)
	 * @see arkham.data.ModifierSource#getModEffects()
	 */
	public List<ModEffect> getModEffects()
	{
		return modEffects;
	}

	/*
	 * (non-Javadoc)
	 * @see arkham.data.ModifierSource#searchModEffects(java.lang.String)
	 */
	public List<ModEffect> searchModEffects(String type)
	{
		List<ModEffect> list = new ArrayList<ModEffect>();
		for (ModEffect mod : modEffects)
		{
			if (mod.getType().equalsIgnoreCase(type))
			{
				list.add(mod);
			}
		}
		return list;
	}

	/*
	 * (non-Javadoc)
	 * @see arkham.data.ModifierSource#searchModEffects(java.util.List, java.lang.String)
	 */
	public void searchModEffects(List<ModEffect> list, String type)
	{
		for (ModEffect mod : modEffects)
		{
			if (mod.getType().equalsIgnoreCase(type))
			{
				list.add(mod);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see arkham.data.ModifierSource#searchModEffects(java.lang.String)
	 */
	public List<ModEffect> searchModEffects(String type, String details)
	{
		List<ModEffect> list = new ArrayList<ModEffect>();
		for (ModEffect mod : modEffects)
		{
			if (mod.getType().equalsIgnoreCase(type) &&
					mod.getDetails().equalsIgnoreCase(details))
			{
				list.add(mod);
			}
		}
		return list;
	}

	/*
	 * (non-Javadoc)
	 * @see arkham.data.ModifierSource#searchModEffects(java.util.List, java.lang.String)
	 */
	public void searchModEffects(List<ModEffect> list, String type, String details)
	{
		for (ModEffect mod : modEffects)
		{
			if (mod.getType().equalsIgnoreCase(type) &&
					mod.getDetails().equalsIgnoreCase(details))
			{
				list.add(mod);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see arkham.data.EffectSource#getUsableEffects()
	 */
	public List<UsableEffect> getUsableEffects()
	{
		return usableEffects;
	}

	/*
     * (non-Javadoc)
     * @see arkham.data.EffectSource#searchUsableEffects(arkham.data.Globals.UsablePhase)
     */
    public List<UsableEffect> searchUsableEffects(UsablePhase phase)
    {
        List<UsableEffect> list = new ArrayList<UsableEffect>();
        for (UsableEffect ueff : usableEffects)
        {
            if (ueff.getPhase().equals(phase) && !ueff.getUsage().equals(EffectUsage.ALWAYS))
            {
                list.add(ueff);
            }
        }
        return list;
    }

    @Override
    public void searchUsableEffects(List<UsableEffect> list,
            UsablePhase phase)
    {
        for (UsableEffect ueff : usableEffects)
        {
            if (ueff.getPhase().equals(phase) && !ueff.getUsage().equals(EffectUsage.ALWAYS))
            {
                list.add(ueff);
            }
        }
    }

	@Override
    public List<UsableEffect> searchAutoUsableEffects(UsablePhase phase)
    {
        List<UsableEffect> list = new ArrayList<UsableEffect>();
        for (UsableEffect ueff : usableEffects)
        {
            if (ueff.getPhase().equals(phase) && ueff.getUsage().equals(EffectUsage.ALWAYS))
            {
                list.add(ueff);
            }
        }
        return list;
    }

	@Override
    public void searchAutoUsableEffects(List<UsableEffect> list,
            UsablePhase phase)
    {
        for (UsableEffect ueff : usableEffects)
        {
            if (ueff.getPhase().equals(phase) && ueff.getUsage().equals(EffectUsage.ALWAYS))
            {
                list.add(ueff);
            }
        }
    }

	@Override
	public List<ModEffect> searchAlwaysModEffects(String type)
	{
	    List<ModEffect> list = new ArrayList<ModEffect>();
        for (ModEffect mod : modEffects)
        {
            if (mod.getType().equalsIgnoreCase(type) &&
                    mod.getUsage().equals(EffectUsage.ALWAYS))
            {
                list.add(mod);
            }
        }
        return list;
	}

	@Override
    public List<ModEffect> searchAlwaysModEffects(String type, String details)
    {
        List<ModEffect> list = new ArrayList<ModEffect>();
        for (ModEffect mod : modEffects)
        {
            if (mod.getType().equalsIgnoreCase(type) &&
                    mod.getDetails().equalsIgnoreCase(details) &&
                    mod.getUsage().equals(EffectUsage.ALWAYS))
            {
                list.add(mod);
            }
        }
        return list;
    }

	@Override
	public String getIdentifier()
	{
	    return "#" + id;
	}

	/**
	 * This method should be called by child classes upon initialization
	 * to populate fields that are common across all types of cards, eg.
	 * description, properties, effects
	 * @param xmlcard the xml card info
	 * @return true on success
	 */
	protected boolean loadCommonFieldsFromXml(CardType xmlcard)
	{
		description = XmlTools.unescapeString(xmlcard.getDescription());
		for (PropertyType prop : xmlcard.getProperty())
		{
			properties.put(prop.getName(), prop.getValue());
		}
		if (XmlTools.isPropertyDefined(xmlcard, "Limit"))
		{
		    try
		    {
		        limit = XmlTools.getIntPropertyValue(xmlcard, "Limit");
		        if (limit <= 0)
		        {
		            throw new NumberFormatException();
		        }
		    }
		    catch (NumberFormatException e)
		    {
		        AHLogger.error(xmlcard.getName() + " has specified an invalid limit: " +
		                XmlTools.getStrPropertyValue(xmlcard, "Limit"));
		        return false;
		    }
		}
		for (ModEffectType xmleffect : xmlcard.getModEffect())
		{
			ModEffect mod = ModEffect.createModEffectFromXml(xmleffect);
			if (mod == null)
			{
				return false;
			}
			mod.setSource(this);
			modEffects.add(mod);
		}
		for (UsableEffectType xmleffect : xmlcard.getUsableEffect())
		{
		    UsableEffect ueff = UsableEffect.createModEffectFromXml(xmleffect);
		    if (ueff == null)
		    {
		        return false;
		    }
		    ueff.setSource(this);
		    usableEffects.add(ueff);
		}

		return true;
	}

	@Override
	public String toString()
	{
		return getType() + ": " + name;
	}

	@Override
	public boolean equals(Object o)
	{
		if (o instanceof InvestigatorCard)
		{
			InvestigatorCard invcard = (InvestigatorCard)o;
			return getType().equals(invcard.getType()) && getName().equals(invcard.getName())
				&& getId() == invcard.getId();
		}
		return false;
	}

	/*
	 * Implemented for sorting purposes
	 */
	public int compareTo(InvestigatorCard invcard)
	{
		if (getType().equals(invcard.getType()))
		{
			return getName().compareTo(invcard.getName());
		}
		else
		{
			return (getType().ordinal() - invcard.getType().ordinal());
		}
	}

	/**
	 * This method creates a data object from the XML representation of the
	 * object. It does so by invoking the appropriate constructor of the
	 * appropriate child class based on the specified type.
	 *
	 * @param xmlcard The XML based card info
	 * @return The data representation of the card
	 */
	public static InvestigatorCard createCardFromXml(CardType xmlcard)
	{
		String strtype = xmlcard.getType();
		if (!InvestigatorCardType.isValidCardType(strtype))
		{
			AHLogger.error(xmlcard.getName() + " has an invalid card type " + strtype);
			return null;
		}

		InvestigatorCardType datatype = InvestigatorCardType.getCardType(strtype);
		switch (datatype)
		{
		case ALLY:
			return Ally.createCardFromXml(xmlcard);
		case COMMON:
			return CommonItem.createCardFromXml(xmlcard);
		case SKILL:
			return Skill.createCardFromXml(xmlcard);
		case SPELL:
			return Spell.createCardFromXml(xmlcard);
		case UNIQUE:
			return UniqueItem.createCardFromXml(xmlcard);
		default:
			AHLogger.error("InvestigatorCard.createCardFromXml", "Unreachable code block");
			return null;
		}
	}
}
